#pragma once

// This is the base exception for a failed API call
#include <engine.core/api_exception.hpp>




typedef boost::error_info<struct tag_fmod_result,FMOD_RESULT>     fmod_result;




/**
 * This class is a specialized exception class for a failed
 * FMOD call. It allows to store the exact call (as a string),
 * the return code (as an FMOD_ERROR) and a description message
 */
class fmod_exception : public api_exception
{
public:

	fmod_exception(const std::string &what) : api_exception(what) {}

	FMOD_RESULT getAPIResult() const
	{
		if(const FMOD_RESULT* err = boost::get_error_info<fmod_result>(*this))
			return *err;
		else
			return FMOD_OK;
	}

	/**
	 * Verifies the given result from a FMOD API call and throws an exception in case the it failed.
	 */
	static void verify_call(FMOD_RESULT res, const char* call, const char* file, const char* function, int line)
	{
		if(res != FMOD_OK)
		{
			BOOSTEXT_THROW_NOINFO(fmod_exception(format("An FMOD API call failed: %1%(%2%)") % readableResult(res) % res)
				<< debug_info_m(file, function, line)
				<< fmod_result(res)
				<< api_call(call));
		}
	}

	static const char* readableResult(FMOD_RESULT res)
	{
		switch(res)
		{
		case FMOD_OK                         : return "FMOD_OK";
		case FMOD_ERR_ALREADYLOCKED          : return "FMOD_ERR_ALREADYLOCKED";
		case FMOD_ERR_BADCOMMAND             : return "FMOD_ERR_BADCOMMAND";
		case FMOD_ERR_CDDA_DRIVERS           : return "FMOD_ERR_CDDA_DRIVERS";
		case FMOD_ERR_CDDA_INIT              : return "FMOD_ERR_CDDA_INIT";
		case FMOD_ERR_CDDA_INVALID_DEVICE    : return "FMOD_ERR_CDDA_INVALID_DEVICE";
		case FMOD_ERR_CDDA_NOAUDIO           : return "FMOD_ERR_CDDA_NOAUDIO";
		case FMOD_ERR_CDDA_NODEVICES         : return "FMOD_ERR_CDDA_NODEVICES";
		case FMOD_ERR_CDDA_NODISC            : return "FMOD_ERR_CDDA_NODISC";
		case FMOD_ERR_CDDA_READ              : return "FMOD_ERR_CDDA_READ";
		case FMOD_ERR_CHANNEL_ALLOC          : return "FMOD_ERR_CHANNEL_ALLOC";
		case FMOD_ERR_CHANNEL_STOLEN         : return "FMOD_ERR_CHANNEL_STOLEN";
		case FMOD_ERR_COM                    : return "FMOD_ERR_COM";
		case FMOD_ERR_DMA                    : return "FMOD_ERR_DMA";
		case FMOD_ERR_DSP_CONNECTION         : return "FMOD_ERR_DSP_CONNECTION";
		case FMOD_ERR_DSP_FORMAT             : return "FMOD_ERR_DSP_FORMAT";
		case FMOD_ERR_DSP_NOTFOUND           : return "FMOD_ERR_DSP_NOTFOUND";
		case FMOD_ERR_DSP_RUNNING            : return "FMOD_ERR_DSP_RUNNING";
		case FMOD_ERR_DSP_TOOMANYCONNECTIONS : return "FMOD_ERR_DSP_TOOMANYCONNECTIONS";
		case FMOD_ERR_FILE_BAD               : return "FMOD_ERR_FILE_BAD";
		case FMOD_ERR_FILE_COULDNOTSEEK      : return "FMOD_ERR_FILE_COULDNOTSEEK";
		case FMOD_ERR_FILE_DISKEJECTED       : return "FMOD_ERR_FILE_DISKEJECTED";
		case FMOD_ERR_FILE_EOF               : return "FMOD_ERR_FILE_EOF";
		case FMOD_ERR_FILE_NOTFOUND          : return "FMOD_ERR_FILE_NOTFOUND";
		case FMOD_ERR_FILE_UNWANTED          : return "FMOD_ERR_FILE_UNWANTED";
		case FMOD_ERR_FORMAT                 : return "FMOD_ERR_FORMAT";
		case FMOD_ERR_HTTP                   : return "FMOD_ERR_HTTP";
		case FMOD_ERR_HTTP_ACCESS            : return "FMOD_ERR_HTTP_ACCESS";
		case FMOD_ERR_HTTP_PROXY_AUTH        : return "FMOD_ERR_HTTP_PROXY_AUTH";
		case FMOD_ERR_HTTP_SERVER_ERROR      : return "FMOD_ERR_HTTP_SERVER_ERROR";
		case FMOD_ERR_HTTP_TIMEOUT           : return "FMOD_ERR_HTTP_TIMEOUT";
		case FMOD_ERR_INITIALIZATION         : return "FMOD_ERR_INITIALIZATION";
		case FMOD_ERR_INITIALIZED            : return "FMOD_ERR_INITIALIZED";
		case FMOD_ERR_INTERNAL               : return "FMOD_ERR_INTERNAL";
		case FMOD_ERR_INVALID_ADDRESS        : return "FMOD_ERR_INVALID_ADDRESS";
		case FMOD_ERR_INVALID_FLOAT          : return "FMOD_ERR_INVALID_FLOAT";
		case FMOD_ERR_INVALID_HANDLE         : return "FMOD_ERR_INVALID_HANDLE";
		case FMOD_ERR_INVALID_PARAM          : return "FMOD_ERR_INVALID_PARAM";
		case FMOD_ERR_INVALID_SPEAKER        : return "FMOD_ERR_INVALID_SPEAKER";
		case FMOD_ERR_INVALID_SYNCPOINT      : return "FMOD_ERR_INVALID_SYNCPOINT";
		case FMOD_ERR_INVALID_VECTOR         : return "FMOD_ERR_INVALID_VECTOR";
		case FMOD_ERR_IRX                    : return "FMOD_ERR_IRX";
		case FMOD_ERR_MAXAUDIBLE             : return "FMOD_ERR_MAXAUDIBLE";
		case FMOD_ERR_MEMORY                 : return "FMOD_ERR_MEMORY";
		case FMOD_ERR_MEMORY_CANTPOINT       : return "FMOD_ERR_MEMORY_CANTPOINT";
		case FMOD_ERR_MEMORY_IOP             : return "FMOD_ERR_MEMORY_IOP";
		case FMOD_ERR_MEMORY_SRAM            : return "FMOD_ERR_MEMORY_SRAM";
		case FMOD_ERR_NEEDS2D                : return "FMOD_ERR_NEEDS2D";
		case FMOD_ERR_NEEDS3D                : return "FMOD_ERR_NEEDS3D";
		case FMOD_ERR_NEEDSHARDWARE          : return "FMOD_ERR_NEEDSHARDWARE";
		case FMOD_ERR_NEEDSSOFTWARE          : return "FMOD_ERR_NEEDSSOFTWARE";
		case FMOD_ERR_NET_CONNECT            : return "FMOD_ERR_NET_CONNECT";
		case FMOD_ERR_NET_SOCKET_ERROR       : return "FMOD_ERR_NET_SOCKET_ERROR";
		case FMOD_ERR_NET_URL                : return "FMOD_ERR_NET_URL";
		case FMOD_ERR_NET_WOULD_BLOCK        : return "FMOD_ERR_NET_WOULD_BLOCK";
		case FMOD_ERR_NOTREADY               : return "FMOD_ERR_NOTREADY";
		case FMOD_ERR_OUTPUT_ALLOCATED       : return "FMOD_ERR_OUTPUT_ALLOCATED";
		case FMOD_ERR_OUTPUT_CREATEBUFFER    : return "FMOD_ERR_OUTPUT_CREATEBUFFER";
		case FMOD_ERR_OUTPUT_DRIVERCALL      : return "FMOD_ERR_OUTPUT_DRIVERCALL";
		case FMOD_ERR_OUTPUT_ENUMERATION     : return "FMOD_ERR_OUTPUT_ENUMERATION";
		case FMOD_ERR_OUTPUT_FORMAT          : return "FMOD_ERR_OUTPUT_FORMAT";
		case FMOD_ERR_OUTPUT_INIT            : return "FMOD_ERR_OUTPUT_INIT";
		case FMOD_ERR_OUTPUT_NOHARDWARE      : return "FMOD_ERR_OUTPUT_NOHARDWARE";
		case FMOD_ERR_OUTPUT_NOSOFTWARE      : return "FMOD_ERR_OUTPUT_NOSOFTWARE";
		case FMOD_ERR_PAN                    : return "FMOD_ERR_PAN";
		case FMOD_ERR_PLUGIN                 : return "FMOD_ERR_PLUGIN";
		case FMOD_ERR_PLUGIN_INSTANCES       : return "FMOD_ERR_PLUGIN_INSTANCES";
		case FMOD_ERR_PLUGIN_MISSING         : return "FMOD_ERR_PLUGIN_MISSING";
		case FMOD_ERR_PLUGIN_RESOURCE        : return "FMOD_ERR_PLUGIN_RESOURCE";
		case FMOD_ERR_RECORD                 : return "FMOD_ERR_RECORD";
		case FMOD_ERR_REVERB_INSTANCE        : return "FMOD_ERR_REVERB_INSTANCE";
		case FMOD_ERR_SUBSOUND_ALLOCATED     : return "FMOD_ERR_SUBSOUND_ALLOCATED";
		case FMOD_ERR_SUBSOUND_CANTMOVE      : return "FMOD_ERR_SUBSOUND_CANTMOVE";
		case FMOD_ERR_SUBSOUND_MODE          : return "FMOD_ERR_SUBSOUND_MODE";
		case FMOD_ERR_SUBSOUNDS              : return "FMOD_ERR_SUBSOUNDS";
		case FMOD_ERR_TAGNOTFOUND            : return "FMOD_ERR_TAGNOTFOUND";
		case FMOD_ERR_TOOMANYCHANNELS        : return "FMOD_ERR_TOOMANYCHANNELS";
		case FMOD_ERR_UNIMPLEMENTED          : return "FMOD_ERR_UNIMPLEMENTED";
		case FMOD_ERR_UNINITIALIZED          : return "FMOD_ERR_UNINITIALIZED";
		case FMOD_ERR_UNSUPPORTED            : return "FMOD_ERR_UNSUPPORTED";
		case FMOD_ERR_UPDATE                 : return "FMOD_ERR_UPDATE";
		case FMOD_ERR_VERSION                : return "FMOD_ERR_VERSION";
		case FMOD_ERR_EVENT_FAILED           : return "FMOD_ERR_EVENT_FAILED";
		case FMOD_ERR_EVENT_INFOONLY         : return "FMOD_ERR_EVENT_INFOONLY";
		case FMOD_ERR_EVENT_INTERNAL         : return "FMOD_ERR_EVENT_INTERNAL";
		case FMOD_ERR_EVENT_MAXSTREAMS       : return "FMOD_ERR_EVENT_MAXSTREAMS";
		case FMOD_ERR_EVENT_MISMATCH         : return "FMOD_ERR_EVENT_MISMATCH";
		case FMOD_ERR_EVENT_NAMECONFLICT     : return "FMOD_ERR_EVENT_NAMECONFLICT";
		case FMOD_ERR_EVENT_NOTFOUND         : return "FMOD_ERR_EVENT_NOTFOUND";
		case FMOD_ERR_EVENT_NEEDSSIMPLE      : return "FMOD_ERR_EVENT_NEEDSSIMPLE";
		case FMOD_ERR_EVENT_GUIDCONFLICT     : return "FMOD_ERR_EVENT_GUIDCONFLICT";
		case FMOD_ERR_MUSIC_UNINITIALIZED    : return "FMOD_ERR_MUSIC_UNINITIALIZ";
		default                              : return "Unknown";
		}
	}
};
///////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * This macro executes the given FMOD call and tests if it returned an error code.
 */
#define FMOD_API(x) fmod_exception::verify_call((x), #x, __FILE__, __FUNCTION__, __LINE__)

/**
 * This macro does not execute the call, but still verifies the result.
 * It helps when certain error codes should not trigger an exception, but
 * others should (without the need to write much code.
 *
 * Usage:
 *  FMOD_RESULT res = m_system->update();
 *  if(res == FMOD_ERR_MEMORY) // DO something
 *  else FMOD_API_F(res, m_system->update());
 */
#define FMOD_API_F(hr, x) fmod_exception::verify_call(hr, #x, __FILE__, __FUNCTION__, __LINE__)
